#!/usr/bin/python
# -*- coding: utf-8 -*-
#coding=utf-8

import os
import sys
import time
import math
import inspect
import json
import struct
import classDemo
import datetimeDemo as tsLog

# 递归遍历一个目录下的所有文件和目录
def traverseFileDir(filePath, maxTravDeep=-1):

    if os.path.isdir(filePath):
        print("dir:", filePath)

        # 在向下一层目录递归之前，看一下现在的函数调用栈，数一下现在是第几层递归调用，如果递归层数比入参maxTravDeep 大，退出结束递归
        # 入参maxTravDeep为-1表示，不判断递归层数限制
        if maxTravDeep > 0:
            stack = inspect.stack()
            callStackDeep = 0
            for frame in stack:
                #print("call stack function", frame.function)
                if frame.function == 'traverseFileDir':
                    callStackDeep += 1
            #print("recurrence call Deep", callStackDeep)
            if callStackDeep > maxTravDeep:
                print("call Deep > maxTravDeep, return")
                return

        # 向下一层目录递归
        for item in os.listdir(filePath):
            itemPath = os.path.join(filePath, item)
            traverseFileDir(itemPath, maxTravDeep)
    else:
        print("file:", filePath)

def traverseFileDir2(filePath, maxTravDeep=-1):

    # scandir 并不会深度遍历 目录下面的子目录
    # 用来做广度优先遍历倒是不错

    dirItr = os.scandir(filePath)
    #print("return from os.scandir", dirItr)

    dirCount = 0
    fileCount = 0
    linkCount = 0
    for item in dirItr:
        if item.is_dir():
            print("scandir dir", item.path)
            dirCount += 1
        elif item.is_file():
            print("scandir file", item.path)
            fileCount += 1
        elif item.is_symlink():
            print("scandir link", item.path)
            linkCount += 1
    print(f"dirCount={dirCount} fileCount={fileCount} linkCount={linkCount} @ {filePath}")

    # 在向下一层目录递归之前，看一下现在的函数调用栈，数一下现在是第几层递归调用，如果递归层数比入参maxTravDeep 大，退出结束递归
    # 入参maxTravDeep为-1表示，不判断递归层数限制
    if maxTravDeep > 0:
        stack = inspect.stack()
        callStackDeep = 0
        for frame in stack:
            # print("call stack function", frame.function)
            if frame.function == 'traverseFileDir2':
                callStackDeep += 1
        # print("recurrence call Deep", callStackDeep)
        if callStackDeep > maxTravDeep:
            print("call Deep > maxTravDeep, return")
            return

    # 向下一层目录递归
    for item in os.scandir(filePath):
        if item.is_dir():
            traverseFileDir2(item.path, maxTravDeep)

def textFileWrite():
    workdir = check2CreateWorkDir()
    poemFilePath = os.path.join(workdir, "poem.txt")

    poem = """
水调歌头·明月几时有
苏轼
丙辰中秋，欢饮达旦，大醉，作此篇，兼怀子由。

明月几时有？把酒问青天。
不知天上宫阙，今夕是何年。
我欲乘风归去，又恐琼楼玉宇，高处不胜寒。
起舞弄清影，何似在人间。

转朱阁，低绮户，照无眠。
不应有恨，何事长向别时圆？
人有悲欢离合，月有阴晴圆缺，此事古难全。
但愿人长久，千里共婵娟。

"""
    writeCount = 0
    try:
        fw = open(poemFilePath, "w")
        writeCount += fw.write(poem)
        writeCount += fw.write(poem)
        fw.close()

    except Exception as err:
        print("open file to write err:", err)
    else:
        print("open file to write succ:", poemFilePath)
    finally:
        if fw:
            fw.close()
        print("textFileWrite try-finally, writeCount=", writeCount)

def textFileRead():
    workdir = check2CreateWorkDir()
    poemFilePath = os.path.join(workdir, "poem.txt")

    try:
        fr = open(poemFilePath, "r")

        # 按字读取
        fr.seek(0)
        str20 = fr.read(20)
        print("开头20字符", str20)

        # 一次性全部读取
        fr.seek(0)
        strAll = fr.read()
        print("所有字符 readCount=", len(strAll))

        # 按行读取
        fr.seek(0)
        lineStr = 'a'
        lineNum = 0
        while lineStr:
            lineStr = fr.readline()
            print(lineNum, lineStr)
            lineNum += 1

        # 按行读取 by enumerate
        fr.seek(0)
        #count = 0
        for index, line in enumerate(fr):
            #count += 1
            print("enumerate", index, line)

    except Exception as err:
        print("open file to read err:", err)
    else:
        print("open file to read succ:", poemFilePath)
    finally:
        if fr:
            fr.close()
        print("textFileRead try-finally")

def jsonFileWrite():
    print("\njsonFileWrite")
    testAccount1 = classDemo.AccountRecord('t1', 'c1', 150)
    print("AccountRecord1", testAccount1)

    # 对象 -》 字典  -》json字符串
    jsonStr = json.dumps(testAccount1.__dict__)
    print("jsonStr", jsonStr)
    workdir = check2CreateWorkDir()
    jsonFilePath = os.path.join(workdir, "o1.json")
    try:
        fw = open(jsonFilePath, "w")
        writeCount = fw.write(jsonStr)
        print("writeCount", writeCount)
    except Exception as err:
        print("open file to write err:", err)
    else:
        print("open file to write succ:", jsonFilePath)
    finally:
        if fw:
            fw.close()
        print("jsonFileWrite try-finally")
    

def jsonFileRead():
    print("\njsonFileRead")
    workdir = check2CreateWorkDir()
    jsonFilePath = os.path.join(workdir, "o1.json")

    #读取json文件 jsonFilePath ，使用其中信息 构建 AccountRecord 对象
    jsonFilePath = os.path.join(workdir, "o1.json")
    try:
        # 文件路径 -> json字典 -> 自定义对象
        fr = open(jsonFilePath, "r")
        print("file open", jsonFilePath, type(fr), fr)
        
        dataFromJson = json.load(fr)
        print("dataFromJson", type(dataFromJson), dataFromJson)
        
        objFromJson = classDemo.AccountRecord()
        # 遍历 dataFromJson，对于每个key value 测试 objFromJson 是否包含这个key，如果有，为objFromJson 设置对应的 value
        for key in dataFromJson:
            if hasattr(objFromJson, key):
                print("find key", key)
                setattr(objFromJson, key, dataFromJson[key])
            else:
                print("not find key", key)
        
        print("objFromJson", objFromJson)

        
    except Exception as err:
        print("open file to read err:", err)
    else:
        print("open file to read succ:", jsonFilePath)
    finally:
        print("jsonFileRead try-finally")

def binFileWrite():
    print("\nbinFileWrite")
    
    # 二进制文件写入
    aList = [classDemo.AccountRecord('t1', 'c1', 150),
             classDemo.AccountRecord('t2t2', 'c2-c2', 130),
             classDemo.AccountRecord('t3t3t3', 'c3_c3_c3', -200, 1)]

    workdir = check2CreateWorkDir()
    binFilePath = os.path.join(workdir, "test.bin")
    writeCount = 0
    try:
        fw = open(binFilePath, "wb")

        # struct 使用 https://blog.csdn.net/qq_37674086/article/details/118914471

        # 先写入列表aList 长度, I 表示 unsigned int
        writeCount += fw.write(struct.pack('I', len(aList)))
        print("aList len write done, writeCount=", writeCount)

        for accObj in aList:
            # accObj.title 写入bin文件
            strBytes = bytes(accObj.title, encoding='UTF-8')
            # 把字符的长度当成数据的内容一起打包。s 表示 char[]
            strPack = struct.pack("I%ds" % (len(strBytes),), len(strBytes), strBytes)
            writeCount += fw.write(strPack)

            # accObj.content 写入bin文件
            strBytes = bytes(accObj.content, encoding='UTF-8')
            # 把字符的长度当成数据的内容一起打包。s 表示 char[]
            strPack = struct.pack("I%ds" % (len(strBytes),), len(strBytes), strBytes)
            writeCount += fw.write(strPack)

            # accObj.amount 写入bin文件，i 表示 int
            writeCount += fw.write(struct.pack('i', accObj.amount))

            # accObj.moneyKind 写入bin文件，i 表示 int
            writeCount += fw.write(struct.pack('i', accObj.moneyKind))

            print("an accObj write done, writeCount=", writeCount, accObj)
    except Exception as err:
        print("open file to write err:", err)
    else:
        print("open file to write succ:", binFilePath)
    finally:
        if fw:
            fw.close()
        print("binFileWrite try-finally, writeCount=", writeCount)

    
def binFileRead():
    print("\nbinFileRead")
    workdir = check2CreateWorkDir()
    binFilePath = os.path.join(workdir, "test.bin")

    intPackSize = struct.calcsize('i')
    unsIntPackSize = struct.calcsize('I')

    readCount = 0
    alist = []

    try:
        fr = open(binFilePath, "rb")

        alistLen = struct.unpack('I', fr.read(unsIntPackSize))[0]
        readCount += unsIntPackSize
        print('from bin file, alistLen=', alistLen, "readCount=", readCount)

        if alistLen>0 and alistLen<1000:
            for index in range(alistLen):
                strByteLen = struct.unpack('I', fr.read(unsIntPackSize))[0]
                strByte = fr.read(strByteLen)
                titleStr = strByte.decode(encoding='UTF-8')
                readCount += unsIntPackSize
                readCount += strByteLen

                strByteLen = struct.unpack('I', fr.read(unsIntPackSize))[0]
                strByte = fr.read(strByteLen)
                contentStr = strByte.decode(encoding='UTF-8')
                readCount += unsIntPackSize
                readCount += strByteLen

                amountNum = struct.unpack('i', fr.read(intPackSize))[0]
                readCount += intPackSize

                moneyKindNum = struct.unpack('i', fr.read(intPackSize))[0]
                readCount += intPackSize

                accObj = classDemo.AccountRecord(titleStr, contentStr, amountNum, moneyKindNum)
                alist.append(accObj)

                print("an accObj read done, readCount=", readCount, accObj)

    except Exception as err:
        print("open file to read err:", err)
    else:
        print("open file to read succ:", binFilePath)
    finally:
        if fr:
            fr.close()
        print("binFileRead try-finally, readCount=", readCount, alist)

def check2CreateWorkDir():
    pythonWorkDirectory = os.getcwd()
    workPath = os.path.join(pythonWorkDirectory, "tmp")
    # 判断结果
    if not os.path.exists(workPath):
        # 如果不存在则创建目录
        os.makedirs(workPath)
    return workPath


def demo():
    tsLog.logTs("fileDemo", preNewLineNum=2)

    # 文件目录遍历
    pythonWorkDirectory = os.getcwd()
    print("Current working directory:", pythonWorkDirectory)
    traverseFileDir(pythonWorkDirectory, maxTravDeep=2)
    traverseFileDir2(pythonWorkDirectory, maxTravDeep=3)

    # 文本文件修改写入
    textFileWrite()

    # 文本文件读取
    textFileRead()

    # json文件读写
    jsonFileWrite()
    time.sleep(1)
    jsonFileRead()

    # 二进制文件读写
    binFileWrite()
    time.sleep(1)
    binFileRead()

    # 二进制文件写入

if __name__ == '__main__':
    demo()